home *** CD-ROM | disk | FTP | other *** search
/ Aminet 4 / Aminet 4 - November 1994.iso / aminet / dev / c / cweb31p9d.lha / CWeb / examples / commonwords.w < prev    next >
Text File  |  1993-12-06  |  28KB  |  726 lines

  1. % COMMONWORDS example of WEB/CWEB portability.  This documentation was
  2. % originally published in the ``Communications of the ACM'', Volume 29,6
  3. % (June 1986), and later in the book ``Literate Programming'', October 1991,
  4. % where it appeared as an example of WEB programming for Pascal by Donald
  5. % E. Knuth.  It has been translated into CWEB by Andreas Scherer to show
  6. % the ease of portability between Pascal/WEB and C/CWEB.  As little changes
  7. % as possible were made, so that the module numbering of the Pascal and
  8. % the C versions are virtually identical.  Restrictions of Pascal mentioned
  9. % in the WEB source were removed and the features of C were used instead.
  10.  
  11. % This program is distributed WITHOUT ANY WARRANTY, express or implied.
  12. % WEB Version --- Don Knuth, 1984
  13. % CWEB Version --- Andreas Scherer, 1993
  14.  
  15. % Copyright (c) 1993 Andreas Scherer
  16.  
  17. % Permission is granted to make and distribute verbatim copies of this
  18. % document provided that the copyright notice and this permission notice
  19. % are preserved on all copies.
  20.  
  21. % Permission is granted to copy and distribute modified versions of this
  22. % document under the conditions for verbatim copying, provided that the
  23. % entire resulting derived work is distributed under the terms of a
  24. % permission notice identical to this one.
  25.  
  26. \font\csc=cmcsc10
  27. \def\PASCAL/{{\csc Pascal\spacefactor1000}}
  28.  
  29. \def\title{COMMONWORDS (C Version 1.1)}
  30. \def\topofcontents{\null\vfill
  31.   \centerline{\titlefont Counting common word frequencies}
  32.   \vskip15pt
  33.   \centerline{(C Version 1.1)}
  34.   \vfill}
  35. \def\botofcontents{\vfill
  36.   \noindent Copyright \copyright\ 1993 Andreas Scherer
  37.   \bigskip
  38.   \noindent Permission is granted to make and distribute verbatim copies of
  39.   this document provided that the copyright notice and this permission
  40.   notice are preserved on all copies.
  41.   \smallskip
  42.   \noindent Permission is granted to copy and distribute modified versions
  43.   of this document under the conditions for verbatim copying, provided that
  44.   the entire resulting derived work is distributed under the terms of a
  45.   permission notice identical to this one.}
  46.  
  47. @* Introduction.  The purpose of this program is to solve the following
  48. problem posed by Jon Bentley@^Bentley, Jon Louis@>:
  49.  
  50. {\narrower\noindent Given a text file and an integer $k$, print the $k$
  51. most common words in the file (and the number of their occurrences) in
  52. decreasing frequency.\par}
  53.  
  54. Jon intentionally left the problem somewhat vague, but he stated that ``a
  55. user should be able to find the one hundred most frequent words in a
  56. twenty-page technical paper (roughly a 50K byte file) without undue
  57. emotional trauma.''
  58.  
  59. Let us agree that a {\it word\/} is a sequence of one or more contiguous
  60. letters; \.{"Bentley"} is a word, but \.{"ain't"} isn't.  The sequence
  61. of letters should be maximal, in the sense that it cannot be lengthened
  62. without including a nonletter.  Uppercase letters are considered equivalent
  63. to their lowercase counterparts, so that the words \.{"Bentley"} and
  64. \.{"BENTLEY"} and \.{"bentley"} are essentially identical.
  65.  
  66. The given problem still isn't well defined, for the file might contain more
  67. than $k$ words, all of the same frequency; or there might not even be as
  68. many as $k$ words.  Let's be more precise:  The most common words are to be
  69. printed in order of decreasing frequency, with words of equal frequency
  70. listed in alphabetic order.  Printing should stop after $k$ words have been
  71. output, if more than $k$ words are present.
  72.  
  73. @ The |stdin| file is assumed to contain the given text.  If it
  74. begins with a positive decimal number (preceded by optional blanks), that
  75. number will be the value of $k$; otherwise we shall assume that $k=100$.
  76. Answers will be sent to the |stdout| file.
  77.  
  78. @d default_k 100 /* use this value if $k$ isn't otherwise specified */
  79.  
  80. @ Besides solving the given problem, this program is supposed to be an
  81. example of the \.{CWEB} system, for people who know some \CEE/ but who
  82. have never seen \.{CWEB} before.  Here is an outline of the program to be
  83. constructed:
  84. @^Differences between \PASCAL/ and \CEE/@>
  85.  
  86. @d FALSE 0
  87. @d TRUE 1
  88.  
  89. @c
  90. #include <stdio.h>
  91. #include <stdlib.h>
  92.  
  93. typedef unsigned char boolean; /* for logical switches */
  94. @<Type declarations@>@/
  95. @<Global variables@>@/
  96. @<Procedures for initialization@>@/
  97. @<Procedures for input and output@>@/
  98. @<Procedures for data manipulation@>@/
  99. @<The main program@>@/
  100.  
  101. @ The main idea of the \.{CWEB} approach is to let the program grow in
  102. natural stages, with its parts presented in roughly the order that they
  103. might have been written by a programmer who isn't especially clairvoyant.
  104.  
  105. For example, each global variable will be introduced when we first know
  106. that it is necessary or desirable; the \.{CWEB} system will take care of
  107. collecting these declarations into the proper place.  We already know about
  108. one global variable, namely the number that Bentley called $k$.  Let us
  109. give it the more descriptive name |max_words_to_print|.
  110.  
  111. @<Global variables@>=
  112. unsigned int max_words_to_print; /* at most this many words will be printed */
  113.  
  114. @ As we introduce new global variables, we'll often want to give them
  115. certain starting values.  This will be done by the |initialize| procedure,
  116. whose body will consist of various pieces of code to be specified when we
  117. think of particular kinds of initialization.
  118.  
  119. @<Procedures for initialization@>=
  120. void initialize(void)
  121.    {
  122.    int i; /* all-purpose index for initializations */
  123.  
  124.    @<Set initial values@>@;
  125.    }
  126.  
  127. @ The \.{CWEB} system, which may be thought of as a preprocessor for
  128. \CEE/, includes a macro definition facility so that portable programs are
  129. easier to write.  For example, we have already defined `|default_k|' to be
  130. 100.  Here are two more examples of \.{CWEB} macros; they allow us to
  131. write, e.g., `|incr(count[p])|' as a convenient abbreviation for the
  132. statement `|count[p]=count[p]+1|'.
  133.  
  134. @d incr(A) ++A /* increment a variable */
  135. @d decr(A) --A /* decrement a variable */
  136.  
  137. @ Originally this program was written in the \PASCAL/ \.{WEB} language.
  138. In difference to \CEE/, \PASCAL/ uses labels and |goto| statements when
  139. abrupting procedures.  This isn't necessary for \CEE/ programs; they
  140. already know the |return| command, so this program will be totally
  141. |goto|-free.
  142. @^Differences between \PASCAL/ and \CEE/@>
  143.  
  144. @* Strategic considerations.  What algorithms and data structures should be
  145. used for Bentley's problem?  Clearly we need to be able to recognize
  146. different occurrences of the same word, so some sort of internal dictionary
  147. is necessary.  There's no obvious way to decide that a particular word of
  148. the input cannot possibly be in the final set, until we've gotten very near
  149. the end of the file; so we might as well remember every word that appears.
  150.  
  151. There should be a frequency count associated with each word, and we will
  152. eventually want to run through the words in order of decreasing frequency.
  153. But there's no need to keep these counts in order as we read through the
  154. input, since the order matters only at the end.
  155.  
  156. Therefore it makes sense to structure our program as follows:
  157.  
  158. @<The main program@>=
  159. void main(void)
  160.    {
  161.    initialize();
  162.    @<Establish the value of |max_words_to_print|@>@;
  163.    @<Input the text, maintaining a dictionary with frequency counts@>@;
  164.    @<Sort the dictionary by frequency@>@;
  165.    @<Output the results@>@;
  166.    }
  167.  
  168. @* Basic input routines.  Let's switch to a bottom-up approach now, by
  169. writing some of the procedures that we know will be necessary sooner or
  170. later.  Then we'll have some confidence that our program is taking shape,
  171. even though we haven't decided yet how to handle the searching or the
  172. sorting.  It will be nice to get the messy details of \CEE/ input out of
  173. the way and off our minds.
  174.  
  175. Here's a function that reads an optional positive integer, returning zero
  176. if none is present at the beginning of the current line.
  177.  
  178. @<Procedures for input and output@>=
  179. int read_int(void)
  180.    {
  181.    int n=0; /* the accumulated value */
  182.    char c; /* the character from the input we're reading */
  183.  
  184.    if( (c=fgetc(stdin)) != EOF ) {
  185.       for(; (c=='\n') || (c==' ') || (c=='\t'); c=fgetc(stdin));
  186.       while(c>='0' && c<='9') {
  187.          n = 10*n + c - '0';
  188.          c=fgetc(stdin);
  189.          }
  190.       }
  191.    return(n);
  192.    }
  193.  
  194. @ We invoke |read_int| only once.
  195.  
  196. @<Establish the value of |max_words_to_print|@>=
  197.    max_words_to_print = read_int();
  198.    if(max_words_to_print==0)
  199.       max_words_to_print = default_k;
  200.  
  201. @ To find words in the |stdin| file, we want a quick way to distinguish
  202. letters from nonletters.  In contrary to \PASCAL/, \CEE/ makes this problem
  203. very easy, because we can fully rely on {\mc ASCII}.  We shall define an
  204. array, |lettercode|, which maps arbitrary characters into the integers
  205. $0\ldots26$.
  206. @^Differences between \PASCAL/ and \CEE/@>
  207.  
  208. If $c$ is a value of type |char| that represents the $k$th letter of the
  209. alphabet, then |lettercode[c]==k|; but if $c$ is a nonletter,
  210. |lettercode[c]==0|.  We assume that $0\leq c\leq255$ whenever $c$ is of
  211. type |char|, i.e., we are dealing with |unsigned char|.
  212.  
  213. @<Global variables@>=
  214. unsigned char lettercode[256]; /* the input conversion table */
  215.  
  216. @ The array |lettercode| is filled with $0$ for all non-letters, and with
  217. the number of the letter in the alphabet.  We won't distinguish between
  218. uppercase and lowercase letters in this program.
  219. @^Differences between \PASCAL/ and \CEE/@>
  220.  
  221. @<Set initial values@>=
  222.    for(i=0; i<=255; ++i)
  223.       lettercode[i]=0; /* non-letter */
  224.  
  225.    lettercode['a'] = lettercode['A'] = 1; @+
  226.    lettercode['b'] = lettercode['B'] = 2;
  227.    lettercode['c'] = lettercode['C'] = 3; @+
  228.    lettercode['d'] = lettercode['D'] = 4;
  229.    lettercode['e'] = lettercode['E'] = 5; @+
  230.    lettercode['f'] = lettercode['F'] = 6;
  231.    lettercode['g'] = lettercode['G'] = 7; @+
  232.    lettercode['h'] = lettercode['H'] = 8;
  233.    lettercode['i'] = lettercode['I'] = 9; @+
  234.    lettercode['j'] = lettercode['J'] = 10;
  235.    lettercode['k'] = lettercode['K'] = 11; @+
  236.    lettercode['l'] = lettercode['L'] = 12;
  237.    lettercode['m'] = lettercode['M'] = 13; @+
  238.    lettercode['n'] = lettercode['N'] = 14;
  239.    lettercode['o'] = lettercode['O'] = 15; @+
  240.    lettercode['p'] = lettercode['P'] = 16;
  241.    lettercode['q'] = lettercode['Q'] = 17; @+
  242.    lettercode['r'] = lettercode['R'] = 18;
  243.    lettercode['s'] = lettercode['S'] = 19; @+
  244.    lettercode['t'] = lettercode['T'] = 20;
  245.    lettercode['u'] = lettercode['U'] = 21; @+
  246.    lettercode['v'] = lettercode['V'] = 22;
  247.    lettercode['w'] = lettercode['W'] = 23; @+
  248.    lettercode['x'] = lettercode['X'] = 24;
  249.    lettercode['y'] = lettercode['Y'] = 25; @+
  250.    lettercode['z'] = lettercode['Z'] = 26;
  251.  
  252. @ Each new word found in the input will be placed into a |buffer| array.
  253. We shall assume that no words are more than $60$ letters long; if a longer
  254. word appears, it will be truncated to 60~characters, and a warning message
  255. will be printed at the end of the run.
  256.  
  257. @d max_word_length 60 /* words shouldn't be longer than this */
  258.  
  259. @<Global variables@>=
  260. unsigned char buffer[max_word_length]; /* the current word */
  261. unsigned int word_length; /* the number of active letters currently in |buffer|;
  262.    $0\ldots$|max_word_length| */
  263. boolean word_truncated; /* was some word longer than |max_word_length|?*/
  264.  
  265. @ @<Set initial values@>=
  266.    word_truncated=FALSE;
  267.  
  268. @ We're ready now for the main input routine, which puts the next word into
  269. the buffer.  If no more words remain, |word_length| is set to zero;
  270. otherwise |word_length| is set to the length of the new word.
  271.  
  272. @<Procedures for input and output@>=
  273. void get_word(void)
  274.    {
  275.    unsigned char c; /* the character we're currently reading */
  276.  
  277.    word_length=0;
  278.    if((c=fgetc(stdin))!=EOF) {
  279.       while(lettercode[c]==0)
  280.          if((c=fgetc(stdin))==EOF)
  281.             return;
  282.       @<Read a word into |buffer|@>@;
  283.       }
  284.    }
  285.  
  286. @ At this point |lettercode[c]>0|, hence $c$ contains the first letter of a
  287. word.
  288.  
  289. @<Read a word into |buffer|@>=
  290.    do @+ {
  291.       if(word_length==max_word_length)
  292.          word_truncated=TRUE;
  293.       else {
  294.          incr(word_length);
  295.          buffer[word_length-1]=lettercode[c];
  296.          }
  297.       c=fgetc(stdin);
  298.       } @+ while(lettercode[c]!=0);
  299.  
  300. @* Dictionary lookup.  Given a word in the buffer, we will want to look for
  301. it in a dynamic dictionary of all words that have appeared so far.  We
  302. expect many words to occur often, so we want a search technique that will
  303. find existing words quickly.  Furthermore, the dictionary should
  304. accommodate words of variable length, and (ideally) it should also
  305. facilitate the task of alphabetic ordering.
  306.  
  307. These constraints suggest a variant of the data structure introduced by
  308. Frank M. Liang@^Liang, Franklin Mark@> in his Ph.D. thesis [{\sl Word
  309. Hy-phen-a-tion by Com-pu-ter}, Stanford University, 1983].  Liang's
  310. structure, which we may call a {\it hash trie}, requires comparatively few
  311. operations to find a word that is already present, although it may take
  312. somewhat longer to insert a new entry.  Some space is sacrificed---we will
  313. need two pointers, a count, and another 5-bit field for each character in
  314. the dictionary, plus extra space to keep the hash table from becoming
  315. congested---but relatively large memories are commonplace nowadays, so the
  316. method seems ideal for the present application.
  317.  
  318. A trie represents a set of words and all prefixes of those words
  319. [cf.~Knuth@^Knuth, Donald Ervin@>, {\sl Sorting and Searching}, Section~6.3].
  320. For convenience, we shall say that all nonempty prefixes of the words in
  321. our dictionary are also words, even though they may not occur as ``words''
  322. in the input file.  Each word (in this generalized sense) is represented
  323. by a |pointer|, which is an index into four large arrays called |link|,
  324. |sibling|, |count|, and |ch|.
  325.  
  326. @d trie_size 32767 /* the largest pointer value */
  327.  
  328. @<Type declarations@>=
  329. typedef unsigned int pointer; /* $0\ldots$|trie_size| */
  330.  
  331. @ One-letter words are represented by the pointers $1$ through $26$.  The
  332. representation of longer words is defined recursively: If $p$ represents
  333. word $w$ and if $1\leq c\leq26$, then the word $w$ followed by the $c$th
  334. letter of the alphabet is represented by |link[p]+c|.
  335.  
  336. For example, suppose that |link[2]==1000|, |link[1005]==2000|, and
  337. |link[2014]==3000|.  Then the word \.{"b"} is represented by the pointer
  338. value~2; \.{"be"} is represented by |link[2]+5==1005|; \.{"ben"} is
  339. represented by~2014; and \.{"bent"} by~3020.  If no longer word beginning
  340. with \.{"bent"} appears in the dictionary, |link[3020]| will be zero.
  341.  
  342. The hash trie also contains redundant information to facilitate traversal
  343. and updating.  If |link[p]| is nonzero, then |link[link[p]]==p|.
  344. Furthermore if |q==link[p]+c| is a ``child'' of $p$, we have |ch[q]==c|;
  345. this additional information makes it possible to go from child to parent,
  346. since |link[q-ch[q]]==link[link[p]]==p|.
  347.  
  348. Children of the same parent are linked together cyclically by |sibling|
  349. pointers: The largest child of $p$ is |sibling[link[p]]|, and the next
  350. largest is |sibling[sibling[link[p]]]|; the smallest child's |sibling|
  351. pointer is |link[p]|.  Continuing our example, if all words in the
  352. dictionary beginning with \.{"be"} start with either \.{"ben"} or
  353. \.{"bet"}, then |sibling[2000]==2020|, |sibling[2020]==2014|, and
  354. |sibling[2014]==2000|.
  355.  
  356. Notice that children of different parents might appear next to each other.
  357. For example, we might have |ch[2019]==6|, for the child of some word such
  358. that |link[p]==2013|.
  359.  
  360. If |link[p]!=0|, the table entry in position |link[p]| is called the
  361. ``header'' of $p$'s children.  The special code value |header| appears in
  362. the |ch| field of each header entry.
  363.  
  364. If $p$ represents a word, |count[p]| is the number of times that the word
  365. has occurred in the input so far.  The |count| field in a header entry is
  366. undefined.
  367.  
  368. Unused positions $p$ have |ch[p]==empty_slot|.  In this case |link[p]|,
  369. |sibling[p]|, and |count[p]| are undefined.
  370.  
  371. @d empty_slot 0
  372. @d header 27
  373. @d move_to_prefix(A) A=link[A-ch[A]]
  374. @d move_to_last_suffix(A) while(link[A]!=0) A=sibling[link[A]]
  375.  
  376. @<Global variables@>=
  377. unsigned int link[trie_size+1],sibling[trie_size+1]; /* $0\ldots$|trie_size| */
  378. unsigned char ch[trie_size+1]; /* |empty_slot|$\ldots$|header| */
  379.  
  380. @ @<Set initial values@>=
  381.    for(i=27; i<=trie_size; ++i)
  382.       ch[i]=empty_slot;
  383.    for(i=1; i<=26; ++i) {
  384.       ch[i]=i; @+ link[i]=count[i]=0; @+ sibling[i]=i-1;
  385.       }
  386.    ch[0]=header; @+ link[0]=0; @+ sibling[0]=26;
  387.  
  388. @ Here's the basic subroutine that finds a given word in the dictionary.
  389. The word will be inserted (with a |count| of zero) if it isn't already
  390. present.
  391.  
  392. More precisely, the |find_buffer| function looks for the contents of
  393. |buffer|, and returns a pointer to the appropriate dictionary location.  If
  394. the dictionary is so full that a new word cannot be inserted, the pointer~$0$
  395. is returned.
  396.  
  397. @d abort_find return(0)
  398.  
  399. @<Procedures for data manipulation@>=
  400. unsigned int find_buffer(void) /* returns values from $0$ to |trie_size| */
  401.    {
  402.    unsigned int i; /* index into |buffer| with values from
  403.       $1$ to |max_word_length| */
  404.    unsigned int p; /* the current word position */
  405.    unsigned int q; /* the next word position */
  406.    unsigned char c; /* current letter code */
  407.    @<Other local variables of |find_buffer|@>@;@#
  408.  
  409.    i=1; @+ p=buffer[0];
  410.    while(i<word_length) {
  411.       incr(i); @+ c=buffer[i-1];
  412.       @<Advance |p| to its child number |c| @>@;
  413.       }
  414.    return(p);
  415.    }
  416.  
  417. @ @<Advance |p| to its child number |c|@>=
  418.    if(link[p]==0)
  419.       @<Insert the firstborn child of |p| and move to it, or |abort_find|@>@;
  420.    else {
  421.       q = link[p] + c;
  422.       if(ch[q] != c) {
  423.          if(ch[q] != empty_slot)
  424.             @<Move |p|'s family to a place where child |c| will fit,
  425.               or |abort_find|@>@;
  426.          @<Insert child |c| into |p|'s family@>@;
  427.          }
  428.       p = q;
  429.    }
  430.  
  431. @ Each ``family'' in the trie has a header location |h==link[p]| such that
  432. child |c| is in location |h+c|.  We want these values to be spread out in
  433. the trie, so that families don't often interfere with each other.
  434. Furthermore we will need to have $26<h\leq{}$|trie_size|${}-26$ if the
  435. search algorithm is going to work properly.
  436.  
  437. One of the main tasks of the insertion algorithm is to find a place for a
  438. new header.  The theory of hashing tells us that it is advantageous to put
  439. the |n|th header near the location $x_n=\alpha n\bmod t$, where
  440. $t={}$|trie_size|${}-52$ and where $\alpha$ is an integer relatively prime
  441. to $t$ such that $\alpha/t$ is approximately equal to the golden ratio
  442. $(\sqrt{5}-1)/2\approx.61803$.  [These locations $x_n$ are about as
  443. ``spread out'' as you can get; see {\sl Sorting and Searching},
  444. pp.~510--511.]
  445.  
  446. @d alpha 20219 /* $\approx.61803$|trie_size| */
  447.  
  448. @<Global variables@>=
  449. pointer x; /* $\alpha n \bmod ($|trie_size|${}-52)$ */
  450.  
  451. @ @<Set initial values@>=
  452.    x = 0;
  453.  
  454. @ We will give up trying to find a vacancy if 1000 trials have been made
  455. without success.  This will happen only if the table is quite full, at
  456. which time the most common words will probably already appear in the
  457. dictionary.
  458.  
  459. @d tolerance 1000
  460.  
  461. @<Get set for computing header locations@>=
  462.    if(x < trie_size - 52 - alpha)
  463.       x += alpha;
  464.    else
  465.       x += alpha - trie_size + 52;
  466.    h = x + 27; /* now $26<h\leq{}$|trie_size|${}-26$ */
  467.    if(h <= trie_size - 26 - tolerance)
  468.       last_h = h + tolerance;
  469.    else
  470.       last_h = h + tolerance - trie_size + 52;
  471.  
  472. @ @<Compute the next trial header location |h|, or |abort_find|@>=
  473.    if(h == last_h)
  474.       abort_find;
  475.    if(h == trie_size - 26)
  476.       h = 27;
  477.    else
  478.       incr(h);
  479.  
  480. @ @<Other local variables of |find_buffer|@>=
  481.    pointer h; /* trial header location */
  482.    int last_h; /* the final one to try */
  483.  
  484. @ @<Insert the firstborn child of |p| and move to it, or |abort_find|@>=
  485.    {
  486.    @<Get set for computing header locations@>@;
  487.    do @+ {
  488.       @<Compute the next trial header location |h|, or |abort_find|@>@;
  489.       } @+ while((ch[h] != empty_slot) || (ch[h+c] != empty_slot));
  490.    link[p] = h; @+ link[h] = p; @+p = h + c;
  491.    ch[h] = header; @+ ch[p] = c;
  492.    sibling[h] = p; @+ sibling[p] = h; @+ count[p] = link[p] = 0;
  493.    }
  494.  
  495. @ The decreasing order of |sibling| pointers is preserved here.  We assume
  496. that |q==link[p]+c|.
  497.  
  498. @<Insert child |c| into |p|'s family@>=
  499.    {
  500.    h = link[p];
  501.    while(sibling[h]>q)
  502.       h = sibling[h];
  503.    sibling[q] = sibling[h]; @+ sibling[h] = q;
  504.    ch[q] = c; @+ count[q] = link[q] = 0;
  505.    }
  506.  
  507. @ There's one complicated case, which we have left for last.  Fortunately
  508. this step doesn't need to be done very often in practice, and the families
  509. that need to be moved are generally small.
  510.  
  511. @<Move |p|'s family to a place where child |c| will fit, or |abort_find|@>=
  512.    {
  513.    @<Find a suitable place |h| to move, or |abort_find|@>@;
  514.    q = h+c; @+ r = link[p]; @+ delta = h-r;
  515.    do @+ {
  516.       sibling[r+delta] = sibling[r] + delta;
  517.       ch[r+delta] = ch[r];
  518.       ch[r] = empty_slot;
  519.       count[r+delta] = count[r];
  520.       link[r+delta] = link[r];
  521.       if(link[r] != 0)
  522.          link[link[r]] = r + delta;
  523.       r = sibling[r];
  524.       } @+ while(ch[r] != empty_slot);
  525.    }
  526.  
  527. @ @<Other local variables of |find_buffer|@>=
  528.    pointer r; /* family member to be moved */
  529.    int delta; /* amount of motion */
  530.    boolean slot_found; /* have we found a new homestead? */
  531.  
  532. @ @<Find a suitable place |h| to move, or |abort_find|@>=
  533.    slot_found = FALSE;
  534.    @<Get set for computing header locations@>@;
  535.    do @+ {
  536.       @<Compute the next trial header location |h|, or |abort_find|@>@;
  537.       if(ch[h+c] == empty_slot) {
  538.          r = link[p];
  539.          delta = h-r;
  540.          while((ch[r+delta]==empty_slot) && (sibling[r]!=link[p]))
  541.             r = sibling[r];
  542.          if(ch[r+delta] == empty_slot)
  543.             slot_found = TRUE;
  544.          }
  545.       } @+ while(!slot_found);
  546.  
  547. @* The frequency counts.  It is, of course, a simple matter to combine
  548. dictionary lookup with the |get_word| routine, so that all the word
  549. frequencies are counted.  We may have to drop a few words in extreme cases
  550. (when the dictionary is full or the maximum count has been reached).
  551.  
  552. @d max_count 32767 /* counts won't go higher than this */
  553.  
  554. @<Global variables@>=
  555.    int count[trie_size+1];
  556.    boolean word_missed; /* did the dictionary get too full? */
  557.    pointer p; /* location of the current word */
  558.  
  559. @ @<Set initial values@>=
  560.    word_missed = FALSE;
  561.  
  562. @ @<Input the text, maintaining a dictionary with frequency counts@>=
  563.    get_word();
  564.    while(word_length) {
  565.       if((p = find_buffer()) == NULL)
  566.          word_missed = TRUE;
  567.       else if(count[p] < max_count)
  568.          incr(count[p]);
  569.       get_word();
  570.       }
  571.  
  572. @ While we have the dictionary structure in mind, let's write a routine
  573. that prints the word corresponding to a given pointer, together with the
  574. corresponding frequency count.
  575.  
  576. For obvious reasons, we put the word into the buffer backwards during this
  577. process.
  578.  
  579. As we can rely on the relative order of the characters in the {\mc ASCII}
  580. set, no conversion array is needed here.  Simple arithmetic will suffice.
  581. @^Differences between \PASCAL/ and \CEE/@>
  582.  
  583. @<Procedures for input and output@>=
  584. void print_word(pointer p)
  585.    {
  586.    pointer q; /* runs through ancestors of |p| */
  587.    unsigned int i; /* index into |buffer|; $1\ldots{}$|max_word_length| */
  588.  
  589.    word_length = 0; @+ q = p; @+ fputc(' ',stdout);
  590.    do @+ {
  591.       incr(word_length);
  592.       buffer[word_length-1] = ch[q];
  593.       move_to_prefix(q);
  594.       } @+ while(q != 0);
  595.    for(i=word_length; i>=1; --i)
  596.       fputc(buffer[i-1]-1+'a',stdout);
  597.    if(count[p] < max_count)
  598.       fprintf(stdout," %d\n",count[p]);
  599.    else
  600.       fprintf(stdout," %d or more\n",max_count);
  601.    fflush(stdout);
  602.    }
  603.  
  604. @* Sorting a trie.  Almost all of the frequency counts will be small, in
  605. typical situations, so we needn't use a general-purpose sorting method.  It
  606. suffices to keep a few linked lists for the words with small frequencies,
  607. with one other list to hold everything else.
  608.  
  609. @d large_count 200 /* smaller counts are in separate lists */
  610.  
  611. @<Global variables@>=
  612. pointer sorted[large_count]; /* list heads */
  613. unsigned int total_words; /* the number of words sorted */
  614.  
  615. @ If we walk through the trie in reverse alphabetical order, it is a simple
  616. matter to change the sibling links so that the words of frequency |f| are
  617. pointed to by |sorted[f-1]|, |sibling[sorted[f-1]]|, $\ldots$ in alphabetical
  618. order.  When |f==large_count|, the words must also be linked in decreasing
  619. order of their |count| fields.
  620.  
  621. The restructuring operations are slightly subtle here, because we are
  622. modifying the |sibling| pointers while traversing the trie.
  623.  
  624. @<Procedures for data manipulation@>=
  625. void trie_sort(void)
  626.    {
  627.    unsigned int k; /* index to |sorted|; $1\ldots{}$|large_count| */
  628.    pointer p; /* current position in the trie */
  629.    unsigned int f; /* current frequency count; $0\ldots{}$|max_count| */
  630.    pointer q, r; /* list manipulation variables */
  631.  
  632.    total_words = 0;
  633.    for(k=1; k<=large_count; ++k)
  634.       sorted[k-1] = 0;
  635.    p = sibling[0]; @+ move_to_last_suffix(p);
  636.    do @+ {
  637.       f = count[p]; @+ q = sibling[p];
  638.       if(f)
  639.          @<Link |p| into the list |sorted[f-1]|@>@;
  640.       if(ch[q] != header) {
  641.          p = q; @+ move_to_last_suffix(p);
  642.          }
  643.       else
  644.          p = link[q]; /* move to prefix */
  645.       } @+ while(p);
  646.    }
  647.  
  648. @ Here we use the fact that |count[0]==0|.
  649.  
  650. @<Link |p| into the list |sorted[f-1]|@>=
  651.    {
  652.    incr(total_words);
  653.    if(f < large_count) { /* easy case */
  654.       sibling[p] = sorted[f-1]; @+ sorted[f-1] = p;
  655.       }
  656.    else {
  657.       r = sorted[large_count-1];
  658.       if(count[p] >= count[r]) {
  659.          sibling[p] = r; @+ sorted[large_count-1] = p;
  660.          }
  661.       else {
  662.          while(count[p] < count[sibling[r]])
  663.             r = sibling[r];
  664.          sibling[p] = sibling[r]; @+ sibling[r] = p;
  665.          }
  666.       }
  667.    }
  668.  
  669. @ @<Sort the dictionary by frequency@>=
  670.    trie_sort();
  671.  
  672. @ After |trie_sort| has done its thing, the sequence of linked lists
  673. |sorted[large_count-1]|, $\ldots$, |sorted[0]| collectively contain all the
  674. words of the input file, in decreasing order of frequency.  Words of equal
  675. frequency appear in alphabetic order.  The individual lists are linked by
  676. means of the |sibling| array.
  677.  
  678. Therefore the following procedure will print the first |k| words, as
  679. required in Bentley's problem.
  680.  
  681. @<Procedures for input and output@>=
  682. void print_common(int k)
  683.    {
  684.    unsigned int f; /* current frequency */
  685.    pointer p; /* current or next word */
  686.  
  687.    f = large_count; @+ p = sorted[f-1];
  688.    do @+ {
  689.       while(p == 0) {
  690.          if(f==1)
  691.             return;
  692.          decr(f); @+ p = sorted[f-1];
  693.          }
  694.       print_word(p); @+ decr(k); @+ p = sibling[p];
  695.       } @+ while(k>0);
  696.    }
  697.  
  698. @* The endgame.  We have recorded |total_words| different words.
  699. Furthermore the global variables |word_missed| and |word_truncated| tell
  700. whether or not any storage limitations were exceeded.  So the remaining
  701. task is simple:
  702.  
  703. @<Output the results@>=
  704.    if(total_words == 0)
  705.       fputs("There are no words in the input!\n",stdout);
  706.    else {
  707.       if(total_words < max_words_to_print) /* we will print all words */
  708.          fputs("Words of the input file, ordered by frequency: \n",stdout);
  709.       else if(max_words_to_print == 1)
  710.          fputs("The most common word and its frequency: \n",stdout);
  711.       else
  712.          fprintf(stdout,"The %d most common words, and their frequencies: \n",
  713.             max_words_to_print);
  714.       print_common(max_words_to_print);
  715.       if(word_truncated)
  716.          fprintf(stdout,"(At least one word had to be shortened to %d letters.)\n",
  717.             max_word_length);
  718.       if(word_missed)
  719.          fputs("(Some input data was skipped, due to memory limitations.)\n",
  720.             stdout);
  721.       }
  722.    fflush(stdout);
  723.  
  724. @* Index.  Here is a list of all uses of all identifiers, underlined at the
  725. point of definition.
  726.